/*************************************************************************
  Crytek Source File.
  Copyright (C), Crytek Studios, 2001-2010.
 -------------------------------------------------------------------------
  $Id$
  $DateTime$

	Managing class for IK Interactions
	Additional logic for using IK (best helper selection, 
	two handed interactions, automatically letting go, 
	timing object animations, etc...)
  
 -------------------------------------------------------------------------
  History:
  - Nov 12th, 2010  : Created by Michelle Martin
 *************************************************************************/

#ifndef __IKINTERACTIONS_SYSTEM_H__
#define __IKINTERACTIONS_SYSTEM_H__

#if _MSC_VER > 1000
# pragma once
#endif


// *************************************************************************/

enum EIK_Interaction_Flags
{
	// do not allow to let go of the object even if the 
	// limb positions get visually awkward
	EIAC_NOREGRIP = (1 << 1) 
};

// *************************************************************************/

enum EIK_Object_Types
{
	// DO NOT CHANGE THE ORDER OR VALUES!
	EIKObject_Default = 0,
	EIKObject_TwoHandedWheel = 1,
	EIKObject_OneHandedWheel = 2,
	EIKObject_WheelHandle = 3,
	EIKObject_Lever = 4,
	EIKObject_Button = 5
	// add more down here and extend GlobalEnums.xml so that LUA knows these are there
};


// *************************************************************************/
//! These parameters must be passed to the CIKInteractionManager when
//! starting a new IK Object Interaction
struct SIKInteractionStartParameters
{
	EntityId					m_characterId;
	EntityId					m_targetId;
	int							m_flags;
	EIK_Object_Types			m_objectType;
	Vec3						m_additionalOffsetPos;
	Quat						m_additionalOffsetRot;
	uint32						m_graphQueryID;
	string						m_graphInputName;  // cannot use CCryName, because graph is case sensitive
	string						m_graphInputValue; // cannot use CCryName, because graph is case sensitive
	CCryName					m_animName;
	float						m_animBlendTime;
	bool						m_animPausesGraph;
	uint						m_animLayer;
	std::map<uint64, CCryName>	m_limbAnimations;  // animations on the limbs while the grip is active (hand poses) (automatically faded in/out)


	//! All limbs involved in the interaction are in here
	std::vector<uint64> m_ikHandles;

	void Reset()
	{
		m_additionalOffsetPos.zero();
		m_additionalOffsetRot.SetIdentity();
		m_ikHandles.clear();
		m_characterId	= 0;
		m_targetId		= 0;
		m_flags				= 0;
		m_objectType	= EIKObject_Default;

		m_graphInputName = "";
		m_graphInputValue = "";
		m_animName = "";
		m_animBlendTime = 0.4f;
		m_animPausesGraph = false;
		m_animLayer = 0;
		m_limbAnimations.clear();
		m_graphQueryID = 0;
	}

	SIKInteractionStartParameters()
	{
		Reset();
	}

	SIKInteractionStartParameters(EntityId characterId, EntityId targetId, int flags)
	{
		Reset();
		m_characterId	= characterId;
		m_targetId		= targetId;
		m_flags				= flags;
		m_objectType	= EIKObject_Default;
		m_ikHandles.clear();
	}

	//! Overrides for convenience
	SIKInteractionStartParameters(EntityId characterId, EntityId targetId, int flags, EIK_Object_Types objectType, uint64 ikHandle01)
	{
		Reset();
		m_characterId = characterId;
		m_targetId		= targetId;
		m_flags				= flags;
		m_objectType	= objectType;
		m_ikHandles.clear();
		m_ikHandles.push_back(ikHandle01);
	}

	//! Overrides for convenience
	SIKInteractionStartParameters(EntityId characterId, EntityId targetId, int flags, EIK_Object_Types objectType, uint64 ikHandle01, uint64 ikHandle02)
	{
		Reset();
		m_characterId = characterId;
		m_targetId		= targetId;
		m_flags				= flags;
		m_objectType	= objectType;
		m_ikHandles.clear();
		m_ikHandles.push_back(ikHandle01);
		m_ikHandles.push_back(ikHandle02);
	}

	//! Overrides for convenience
	SIKInteractionStartParameters(EntityId characterId, EntityId targetId, int flags, EIK_Object_Types objectType, const std::vector<uint64>& ikHandles)
	{
		Reset();
		m_characterId = characterId;
		m_targetId		= targetId;
		m_flags				= flags;
		m_objectType	= objectType;
		m_ikHandles.clear();
		m_ikHandles = ikHandles;
	}
};



// *************************************************************************/



class CIKInteractionManager : public IAnimationGraphStateListener
{
public:
	CIKInteractionManager();
	~CIKInteractionManager();

	// -------------------------------------------------------------------------

	//! Returns an interactionId if successful, -1 if not
	int		StartNewIKInteraction(const SIKInteractionStartParameters& params);

	//! Stops the interaction with the specified interactionId (returned from StartNewIKInteraction)
	//! returns true if an interaction was found and stop initiated, false if not
	bool	StopIKInteraction(int interactionId, float blendOutTime = 0.7f);

	//! Stops any interaction that is active between the specified character and object
	void	StopIKInteraction(EntityId charId, EntityId objectID, float blendOutTime = 0.7f);

	//! Immediately stops all interactions - called when the Editor Game mode is left
	void	StopAllIKInteractions();

	//! Returns 0 if the interaction is not existing or in the finished state
	//! Returns 1 if the interaction is active
	//! Returns 2 if the interaction is active but currently fading out (for finishing, not regripping)
	int		IsValidInteraction(int interactionId);

	//! Sets a new offset for a running interaction.
	//! This is useful for adjusting an offset in Sandbox editor when setting up the objects.
	//! It is not recommended to use this during game-play, as it will recalculate the offset every time it is called.
	void	SetOffsetForInteraction(int interactionId, QuatT newOffset);

	//! This is the place where most of the script callbacks to the IK Object are made.
	//! Depending on the state of the interaction.
	void	Update(float frameTime);

	//! Enables or disbales the system.
	//! When disabled, all active interactions are immediately stopped and the system will 
	//! no longer update.
	void	EnableSystem(bool enable);

	//------------ implementation of IAnimationGraphStateListener
	virtual void SetOutput( const char * output, const char * value ) {}
	virtual void DestroyedState(IAnimationGraphState* ) {};
	virtual void QueryComplete( TAnimationGraphQueryID queryID, bool succeeded );
	//------------ implementation of IAnimationGraphStateListener

private:

	//! used for helper enumeration on the target object
	//! these are the supported type (see also IKTarget system)
	enum EIKHelperTypes
	{
		IKHelper_EntityPos,
		IKHelper_Attachment,
		IKHelper_Helper,
		IKHelper_Bone
	};
	struct IKHelper
	{
		EIKHelperTypes m_helperType;
		const char*		 m_name;
		QuatT					 m_offset;
		bool					 m_offsetIsGlobal;

		IKHelper(const char* name, EIKHelperTypes helperType)
		{
			m_helperType = helperType;
			m_name = name;
			m_offset.SetIdentity();
			m_offsetIsGlobal = false;
		}

		bool operator ==( const IKHelper& b) const
		{
			return (m_helperType == b.m_helperType && m_name == b.m_name);
		}
	};
	typedef  std::vector< IKHelper >  TIKHelperList;

// *************************************************************************/

	enum EIKState
	{
		IKState_InteractionAdded,
		IKState_WaitForAnimation,
		IKState_StartIK,
		IKState_FadingIn,
		IKState_Active,
		IKState_ReGrip_FadeOut,
		IKState_FadingOut,
		IKState_Finished
	};

	//! These parameters are stored for currently active interactions
	struct SActiveInteraction
	{
		int				m_interactionId;
		int				m_flags;
		EntityId		m_targetEntity;				//! needed for the callbacks to the script and finding new helpers in case of re-grip
		EIKState		m_interactionState;
		EIK_Object_Types m_objectType;
		Vec3			m_additionalOffsetPos;
		Quat			m_additionalOffsetRot;
		float			m_regripAllowTimer;			//! counts down from a set time to zero

		// animation related data, for restoring anim graph
		bool			m_graphPaused;
		int				m_graphInputID;
		string			m_graphPrevValue;
		std::map<uint64, CCryName>	m_limbAnimations;  // animations on the limbs while the grip is active (hand poses) (automatically faded in/out)


		SIKInteractionStartParameters* m_startParams;

		// runtime helper data
		float			m_timerHelper;				//! used for some blending operations (for example re-grip)
		Vec3			m_helpVec;						//! used for temp data storage (for example re-grip)

		//! All limbs involved in the interaction are in here
		std::vector<uint64> m_ikHandles;

		//! The current selected helpers
		TIKHelperList	m_selectedHelpers;

		SActiveInteraction(int id, SIKInteractionStartParameters& startParams)
		{
			m_interactionId = id;
			m_flags = startParams.m_flags;
			m_targetEntity = startParams.m_targetId;
			m_interactionState = IKState_InteractionAdded;
			m_objectType = startParams.m_objectType;
			m_ikHandles = startParams.m_ikHandles;
			m_helpVec.Set(0,0,0);
			m_regripAllowTimer = 0.0f;
			m_additionalOffsetPos = startParams.m_additionalOffsetPos;
			m_additionalOffsetRot = startParams.m_additionalOffsetRot;
			m_limbAnimations = startParams.m_limbAnimations;
			m_startParams = new SIKInteractionStartParameters();
			*m_startParams = startParams;

			m_graphPaused = false;
			m_graphInputID = -1;

		}
	};
	//! holding all ongoing interactions for one character 
	typedef		std::vector<SActiveInteraction>		TCharacterInteractions;

// *************************************************************************/

	struct SIKSelectionHelper
	{
		short		inUseByLimb;
		short		helperIndex;
		QuatT		worldLocation;

		SIKSelectionHelper()
		{
			inUseByLimb = -1;
		}
	};

	struct SIKLimbHelper
	{
		QuatT	worldLocation;
		float distanceToTarget;
	};

	typedef std::vector<SIKSelectionHelper> TIKSelectionHelperList;
	typedef std::vector<SIKLimbHelper>			TIKLimbRootPositions;

// *************************************************************************/

private:

	//! Retrieves the active interactions and outputs the character entity to characterId
	SActiveInteraction*		GetInteractionById(int interactionId, EntityId& characterId);

	//! Validity check of the parameters passed in. 
	//! This function checks for valid interfaces and entities.
	//! Returns a combination of EIK_Interaction_ErrorCodes
	//! Returns IK_OK if everything is fine.
	int		CheckParameters(const SIKInteractionStartParameters& params);

	//! Goes through the list of IK Handles that have been passed in and
	//! selects one or multiple to be used in the interaction.
	//! Selection is based on flags and object type, the vector inside params will
	//! be modified (all invalid or unused handles are removed)
	//! Returns a combination of EIK_Interaction_ErrorCodes
	//! Returns IK_OK if everything is fine.
	int		SelectIKHandles(SIKInteractionStartParameters& params);

	//! Enumerates and chooses IK Helpers for all limbs and creates IK Targets for them (and thus starts the IK).
	//! This function is called at the beginning of an interaction and again when a re-grip is initiated
	int		SelectAndCreateIKTargets(SIKInteractionStartParameters& params, TIKHelperList& selectedHelpers, bool ignoreSelectedHelpers, Vec3 &helpVec, float fBlendTime = 0.55f );

	//! Enumerates all helpers on the target object regardless of their type.
	//! Fills the ikHelperList with the found markup.
	//! If no Markup of any type can be found, the function will add a fallback
	//! helper using the entity's position. 
	//! Returns IK_OK if everything is fine.
	int		EnumerateIKHelpersOnObject(const SIKInteractionStartParameters& params, TIKHelperList& ikHelperList);

	//! helper functions to do the actual enumeration (attachments, exported helpers, bone names)
	void	EnumerateIKHelpers_Attachment(const SIKInteractionStartParameters& params, TIKHelperList& ikHelperList, string& helperPrefix);
	void	EnumerateIKHelpers_Helper		 (const SIKInteractionStartParameters& params, TIKHelperList& ikHelperList, string& helperPrefix);
	void	EnumerateIKHelpers_Bone			 (const SIKInteractionStartParameters& params, TIKHelperList& ikHelperList, string& helperPrefix);
	bool	NameHasPrefix(const char* name, string& prefix);

	//! Chooses an appropriate helper for each IK Limb that will be part of the interaction from the enumerated list.
	//! If there are less helpers than limbs the function will remove the additional limbs from the interaction.
	//! The ikHelperList is restructured so that it matches the limbs the helpers are assigned to by index (limb 1 uses helper 1 etc..)
	//! Returns IK_OK if everything is fine.
	int		ChooseIKHelperForLimbs(SIKInteractionStartParameters& params, TIKHelperList& ikHelperList, TIKHelperList& ignoreSelectedHelpers, Vec3 &helpVec);

	//! Helper function that chooses exactly two helpers for two limbs, assuming this is a human wanting to
	//! grip a big wheel with two hands
	int		ChooseIKHelpers_TwoHandedWheel( SIKInteractionStartParameters &params, TIKHelperList &ikHelperList, TIKHelperList& ignoreSelectedHelpers, Vec3& OT1OT2 );

	//! Calculates offsets for all chosen IK Targets to adjust for the hand (or other end-effectors)
	//! Can modify the helpVec in the process
	int		CalculateOffsetsForIKHelpers(SIKInteractionStartParameters& params, TIKHelperList& ikHelperList, Vec3 &helpVec);

	//! Starts the IK for all limbs using the IK Target System
	//! Returns IK_OK if everything is fine.
	int		CreateIKTargetsForAllLimbs(SIKInteractionStartParameters& params, const TIKHelperList& ikHelperList, float fBlendTime = 0.55f );

	//! Retrieves the vector with the currently active IK Object Interactions that this character is performing.
	//! returns NULL if no interactions are yet present for this character
	TCharacterInteractions*		GetActiveInteractionsForCharacter(EntityId entityID);

	//! Helper function to retrieve the current world location of any ikHelper on the object
	const QuatT	GetIKHelperLocation(EntityId entityID, const IKHelper& ikHelper) const;

	//! Helper function to make a list of all IKHelpers and their position, ready to be 
	//! used in the IK Helper selection function ::ChooseIKHelperForLimbs
	//! returns the average middle world position of all helpers
	Vec3 CalculateWorldLocationsOfIKHelpers( TIKSelectionHelperList &selectionHelperList, TIKHelperList &ikHelperList, EntityId targetId );

	//! Helper function to make a list of all IKLimbs world positions, ready to be
	//! used in the IK Helper selection function ::ChooseIKHelperForLimbs
	void CalculateIKHandleRootLocations( TIKLimbRootPositions &limbRootPositions, SIKInteractionStartParameters &params );

	void UpdateFadingOut(EntityId entity, SActiveInteraction& activeInteraction);

	void UpdateFadingIn(EntityId entity, SActiveInteraction& activeInteraction);

	//! Helper function that detects whether this interaction needs to be paused for a regrip of the limb(s)
	bool	IsRegripNeeded(EntityId entity, SActiveInteraction& activeInteraction);

	//! Starts a re-grip (fades out the ik targets, notifies the object etc
	void	InitiateRegrip(EntityId entity, SActiveInteraction& activeInteraction);

	//! Helper function to start the animations on AnimationGraph and/or directly on the skeleton
	void StartMainAnimations( EntityId charEntityID, SActiveInteraction &interaction );

	SIKInteractionStartParameters CreateStartParamsFromInteraction(EntityId entity, const SActiveInteraction& activeInteraction);

	//! internal helper function to stop an interaction and do all the cleanup involved (unpausing graph, notifying script...)
	void StopInteraction( SActiveInteraction* activeInteraction, EntityId charEntityID, float blendOutTime ) ;

	//! Prints out one or more error messages and returns -1.
	//! Helper function to be used for returning -1
	//! Usage: return IKFailed(errorCodeCombination)
	int	IKFailed(int errorCodes);


	enum EIK_Interaction_ErrorCodes {
		IK_OK								=  0,
		IKErr_No_Valid_CharacterEntityID	=  1,
		IKErr_No_Valid_CharacterInterface	=  2,
		IKErr_No_Valid_TargetEntityID		=  4, 
		IKErr_No_Valid_IK_Handles			=  8, 
		IKErr_No_Helper_Selection_Possible	= 16, 
		IKErr_Unknown_Helper_Type			= 32, 
		IKErr_Need_Two_Limbs				= 64,
		IKErr_System_Is_Disabled			= 128  //, 256, 512, 1024, 2048 ...

		// Note:
		// Also extend the IKFailed function to print out an error message for every new error code you add.
	};

	// SCRIPT Helper functions
	// =============================

	//! Calls the Script Function OnInteractionTrigger to inform the IK Object that the interaction has started/stopped
	void Script_OnInteractionTrigger(EntityId targetEntityId, EntityId character, bool isStarting);

	//! Calls the SCript function CharacterIsFollowingObject to inform the IK object whether the character is holding on to the object or not
	void Script_CharacterIsFollowingObject(EntityId targetEntityId, EntityId character, bool ikIsBlendedIn);

	ScriptAnyValue EntityIdToScript( EntityId id );

private:

	bool	m_systemActive;
	int		m_nextInteractionId;

	std::map<EntityId, TCharacterInteractions*> m_activeInteractions;

#ifdef _DEBUG
	struct SDebugInformation
	{
		Vec3 pos1;
		Vec3 dir1;
		Vec3 pos2;
		Vec3 dir2;
		Vec3 pos3;
		Vec3 dir3;
		Vec3 sphere1;
		Vec3 sphere2;
		Vec3 sphere3;

		void Render();
		SDebugInformation()
		{
			pos1.zero();
			dir1.zero();
			pos2.zero();
			dir2.zero();
			pos3.zero();
			dir3.zero();
			sphere1.zero();
			sphere2.zero();
			sphere3.zero();
		};
	};
	SDebugInformation m_debugInfoRenderer;
#endif // _DEBUG


};




#endif		// __IKINTERACTIONS_SYSTEM_H__
